// Created by Windward Studios - no copyright is claimed. This code can be used in
// any manner by anyone for any reason. There is no copyright of any kind on it. You may
// use it in commercial products. You may change it without sharing those changes.
// We ask that you keep the "created by Windward Studios" in a comment at the top.

using System;
using System.Collections;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.IO;
using Kailua.net.windward.utils;
using Microsoft.Win32;

// bugbug
// 1: get FK:PK relationships for columns.
// 3: get schema owner for tables, views, stored procedures
// 4: get table/view/stored procedure/column descriptions.
// this may help - http://msdn.microsoft.com/en-us/library/ms254969(VS.80).aspx

namespace net.windward.utils.ado.DB2
{
    /// <summary>
    /// This is similiar to DbProviderFactory and provides additional functionality for each provider. All properties to list
    /// if a provider can perform any task in it's child classes is provider here, not in the child classes. This
    /// class can return IWrServer objects for a given server/instance that exists on a computer. The ODBC/OleDb
    /// concept of a Provider (which is different from a DbProviderFactory) is handled as a property set in the
    /// WrProviderFactory class so all other cases do not need to implement a class for that layer.
    /// This is called WrVendor instead of WrProvider because it is different from DbProviderFactory and because
    /// we use the term Provider for the ODBC/OleDb providers.
    /// This is the class for the IBM DB2 database.
    /// </summary>
    public class WrDB2Vendor : WrVendor
    {
        /// <summary>
        /// The DbProviderFactory class for this vendor
        /// </summary>
        public const string FactoryClass = "IBM.Data.DB2";

        /// <summary>
        /// The suggested name for this vendor.
        /// </summary>
        public const string FactoryName = "DB2";

		private WrDB2Server[] allServers;

        /// <summary>
        /// Creates a DB2 vendor object.
        /// </summary>
        public WrDB2Vendor()
            : base(FactoryName, "The standard IBM DB2 connector", FactoryClass)
        {
            syntax = new WrDB2Syntax(providerFactory);
        }

        /// <summary>
        /// Creates a DB2 vendor object.
        /// </summary>
        /// <param name="name">The friendly name of the provider.</param>
        /// <param name="description">The description of this provider (optional).</param>
        /// <param name="providerClass">The class of the provider.</param>
        public WrDB2Vendor(string name, string description, string providerClass)
            : base(name, description, providerClass)
        {
            syntax = new WrDB2Syntax(providerFactory);
        }

        #region WrVendor properties

        /// <summary>
        /// True if can enumerate databases on a server. This is implemented seperately from
        /// CanCreateDataSourceEnumerator because DB2 and SqlServer implement CreateDataSourceEnumerator differently.
        /// </summary>
        public override bool CanEnumerateDatabases
        {
            get { return true; }
        }


        /// <summary>
        /// True if can enumerate running servers from this vendor. This is implemented seperately from
        /// CanCreateDataSourceEnumerator because DB2 and SqlServer implement CreateDataSourceEnumerator differently.
        /// </summary>
        public override bool CanEnumerateServers
        {
            get { return true; }
        }

        /// <summary>
        /// True if can launch the administrator for this vendor. This will return true if it can find the admin
        /// program file. In that case the launch could still fail.
        /// </summary>
        public override bool CanLaunchAdministrator
        {
            get
            {
                return GetAdminFilename() != null;
            }
        }

        /// <summary>
        /// True if can run DDL scripts.
        /// </summary>
        public override bool CanRunScripts
        {
            get { return false; }
        }

        /// <summary>
        /// True if can use the WindowsIdentity of the caller eliminating the need to pass a username and password.
        /// </summary>
        public override bool CanUseTrustedConnection
        {
            get { return true; }
        }

        /// <summary>
        /// Returns true if this connector is installed on the system. To be true the connector must be in the GAC and must
        /// be listed in machine.config.
        /// </summary>
        public override bool IsInstalled
        {
            get
            {
                try
                {
                    DbProviderFactory provider = DbProviderFactories.GetFactory(FactoryClass);
                	return provider != null;
                }
                catch (Exception)
                {
                    return false;
                }
            }
        }

        /// <summary>
        /// Create a server object.
        /// </summary>
        /// <param name="server">The name of the server the database is on.</param>
        public override IWrServer CreateServer(string server)
        {

            // need the database enumeration
            if (!string.IsNullOrEmpty(server))
            {
                IWrServer[] servers = _GetServers(false);
                foreach (IWrServer srvrOn in servers)
                    if (srvrOn.Name.ToUpper() == server.ToUpper())
                        return srvrOn;
            }
            return new WrDB2Server(providerFactory, server, new string[0]);
        }

        #endregion

        #region WrVendor methods

        /// <summary>
        /// Returns the parameters from a connection string making the best guess. Will return null for items it could not determine.
        /// </summary>
        /// <param name="connectionString">The connection string to parse.</param>
        /// <returns>The parameters.</returns>
        public override WrConnectionParams ConnectionParams(string connectionString)
        {

            if (string.IsNullOrEmpty(connectionString))
                return new WrConnectionParams();

            Trap.trap();
            DbConnectionStringBuilder builder = providerFactory.CreateConnectionStringBuilder();
            builder.ConnectionString = connectionString;
            return new WrConnectionParams(builder["Server"] as string, builder["Database"] as string, builder["UID"] as string, builder["PWD"] as string);
        }

        /// <summary>
        /// A connection string for a database on a server.
        /// </summary>
        /// <param name="server">The server to access (ex: localhost).</param>
        /// <param name="database">The database to connect to.</param>
        /// <param name="credentials">The user credentials to access the database.</param>
        /// <param name="showPassword">True to include the password, false to add the password as *****.</param>
        /// <returns>The connection string for this database on this server.</returns>
        public override string ConnectionString(string server, string database, WrCredentials credentials, bool showPassword)
        {
            return ConnectionString(providerFactory, server, database, credentials, showPassword);
        }

        internal static string ConnectionString(DbProviderFactory provider, string server, string database, WrCredentials credentials, bool showPassword)
        {

            DbConnectionStringBuilder connStrBuilder = provider.CreateConnectionStringBuilder();
            if (!string.IsNullOrEmpty(server))
                connStrBuilder.Add("server", server);
            if (!string.IsNullOrEmpty(database))
                connStrBuilder.Add("database", database);
            if (!credentials.UseWindowsIdentity)
            {
                if (!string.IsNullOrEmpty(credentials.Username))
                    connStrBuilder.Add("Uid", credentials.Username);
                if (!string.IsNullOrEmpty(credentials.Password))
                    connStrBuilder.Add("Pwd", showPassword ? credentials.Password : "*****");
            }
            return connStrBuilder.ConnectionString;
        }

        /// <summary>
        /// Returns a new instance of the provider's class that implements the WrCommand class.
        /// </summary>
        /// <returns>A new instance of WrCommand.</returns>
        public override WrCommand CreateCommand()
        {
            Trap.trap();
            return new WrDB2Command(providerFactory);
        }

		/// <summary>
        /// All servers from this vendor on the network. This may not get all due to routers not passing
        /// on a broadcast or delays in some servers responding.
        /// </summary>
        /// <exception cref="WrDbException">Thrown if this provider does not support enumerate servers.</exception>
        /// <returns>All servers found on the sub-net.</returns>
		public override IWrServer[] GetServers()
		{
			return _GetServers(true);
		}

    	internal IWrServer[] _GetServers(bool takeYourTime)
        {

			if (allServers != null)
				return allServers;
			if (! takeYourTime)
				return new WrDB2Server[0];

            // IBM returns one entry for each server/instance/database. We need to save the databases to give to
            // each server so they don't have to be enumerated in the database (as it takes a couple of seconds).
            try
            {
                DbDataSourceEnumerator dsEnum = providerFactory.CreateDataSourceEnumerator();
                if (dsEnum == null)
                    return allServers = new WrDB2Server[0];

                DataTable sources = dsEnum.GetDataSources();
				return allServers = ReadServers(providerFactory, sources);
            }
            catch (Exception)
            {
                return allServers = new WrDB2Server[0];
            }
        }

		internal static WrDB2Server[] ReadServers(DbProviderFactory providerFactory, DataTable sources)
    	{
    		int ordServer = sources.Columns.IndexOf("ServerName");
    		int ordInstance = sources.Columns.IndexOf("InstanceName");
    		int ordDatabase = sources.Columns.IndexOf("DatabaseName");
    		Hashtable servers = new Hashtable();

    		foreach (DataRow row in sources.Rows)
    		{
    			string serverName = (string)row[ordServer];
    			string instance = row.IsNull(ordInstance) ? null : (string)row[ordInstance];
    			// As best as I can tell a value of DB2 means do not include the instance when connecting.
    			if (!string.IsNullOrEmpty(instance) && (instance != "DB2"))
    				serverName += "\\" + instance;
    			string databaseName = (string)row[ordDatabase];
    			if (servers.Contains(serverName))
    				((ArrayList)servers[serverName]).Add(databaseName);
    			else
    			{
    				ArrayList dbList = new ArrayList();
    				dbList.Add(databaseName);
    				servers.Add(serverName, dbList);
    			}
    		}

    		ArrayList rtn = new ArrayList();
    		foreach (DictionaryEntry de in servers)
    			rtn.Add(new WrDB2Server(providerFactory, (string)de.Key, (string[])((ArrayList)de.Value).ToArray(typeof(string))));

    		return (WrDB2Server[])rtn.ToArray(typeof(WrDB2Server));
    	}

    	/// <summary>
        /// Launch the database admin tool.
        /// </summary>
        /// <returns>True if launched successfully.</returns>
        public override bool RunAdministrator()
        {
            Process proc = Process.Start(GetAdminFilename());
            return (proc != null) && (!proc.HasExited);
        }

        private static string GetAdminFilename()
        {

            RegistryKey key = Registry.LocalMachine.OpenSubKey("SOFTWARE\\IBM\\DB2\\");
            if (key == null)
                return null;
            string exe = (string)key.GetValue("DB2 Path Name");
            Trap.trap(exe == null);
            if (exe != null)
            {
                exe = Path.GetFullPath(Path.Combine(exe, "BIN\\db2cc.bat"));
                if (!File.Exists(exe))
                    exe = null;
            }
            key.Close();
            return exe;
        }
        #endregion
    }
}
